﻿#region [Using directives]
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
#endregion

namespace InteractingSquares
{
  /// <summary>
  /// A simple square shape
  /// </summary>
  public class Square : Path, INotifyPropertyChanged
  {
    #region [Needs]
    private int m_Direction;
    public event PropertyChangedEventHandler PropertyChanged;
    private double m_Speed;
    private Point m_Center;
    private int m_Size;
    private double m_Top;
    private double m_Bottom;
    private double m_Left;
    private double m_Right;
    #endregion

    #region [Ctor]
    public Square(Point Center, int Size)
    {
      Random random = new Random();
      Color SquareColor = Color.FromArgb(150, (byte)random.Next(255), (byte)random.Next(255), (byte)random.Next(255));
      Init(Center, Size, SquareColor);
    }

    public Square(Point Center, int Size, Color Color)
    {
      Init(Center, Size, Color);
    }
    #endregion

    #region [Properties]
    public double Speed
    {
      get
      {
        return m_Speed;
      }
      set
      {
        m_Speed = value;
        NotifyPropertyChanged("Speed");
      }
    }
    public Color FillColor
    {
      get;
      set;
    }

    public Point Center
    {
      get
      {
        return m_Center;
      }
      set
      {
        m_Center = value;
        Top = Center.Y - Size / 2;
        Bottom = Center.Y + Size / 2;
        Left = Center.X - Size / 2;
        Right = Center.X + Size / 2;
        NotifyPropertyChanged("Center");
      }
    }

    public int Size
    {
      get
      {
        return m_Size;
      }
      set
      {
        m_Size = value;
        NotifyPropertyChanged("Size");
      }
    }

    public double Top
    {
      get
      {
        return Math.Round(m_Top, 0);
      }
      set
      {
        m_Top = value;
        NotifyPropertyChanged("Top");
      }
    }

    public double Left
    {
      get
      {
        return Math.Round(m_Left);
      }
      set
      {
        m_Left = value;
        NotifyPropertyChanged("Left");
      }
    }

    public double Right
    {
      get
      {
        return Math.Round(m_Right);
      }
      set
      {
        m_Right = value;
        NotifyPropertyChanged("Right");
      }
    }

    public double Bottom
    {
      get
      {
        return Math.Round(m_Bottom);
      }
      set
      {
        m_Bottom = value;
        NotifyPropertyChanged("Bottom");
      }
    }

    /// <summary>
    /// Gets or sets the direction.
    /// </summary>
    /// <value>
    /// The direction in degrees starting with 0° in right direction clockwise. Expects a value between 0 and 359.
    /// </value>
    public int Direction
    {
      get
      {
        return m_Direction;
      }
      set
      {
        if (value >= 0 && value < 360)
        {
          m_Direction = value;
          NotifyPropertyChanged("Direction");
        }
      }
    }
    #endregion

    #region [Public methods]
    public void Move()
    {
      var x = this.RenderTransform as TranslateTransform;
      if (x == null)
        this.RenderTransform = new TranslateTransform();

      CollisionCheck();

      Point NewPoint = GetNewCoordinates();

      (this.RenderTransform as TranslateTransform).X = Left;
      (this.RenderTransform as TranslateTransform).Y = Top;

      Center = NewPoint;
    }
    #endregion

    #region [Private methods]
    private void Init(Point Center, int Size, Color Color)
    {
      this.Center = Center;
      this.FillColor = Color;
      this.Size = Size;
      CreatePathData();
    }

    private void CreatePathData()
    {
      Size = Math.Abs(Size);

      RectangleGeometry geometry = new RectangleGeometry();
      geometry.Rect = new Rect(Center.X - Size / 2, Center.Y - Size / 2, Size, Size);

      SolidColorBrush FillBrush = new SolidColorBrush();
      FillBrush.Color = this.FillColor;

      this.Fill = FillBrush;

      this.Data = geometry;
    }

    private void CollisionCheck()
    {
      var BoundsCollideResult = CollidesWithBounds();
      if (BoundsCollideResult != CollisionResult.None)
      {
        if (BoundsCollideResult == CollisionResult.Top || BoundsCollideResult == CollisionResult.Bottom)
        {
          Direction = Math.Abs(360 - Direction);
        }
        if (BoundsCollideResult == CollisionResult.Left || BoundsCollideResult == CollisionResult.Right)
        {
          if (Direction >= 0 && Direction <= 180)
          {
            int NewDirection = Math.Abs(180 - Direction);
            if (NewDirection == 360)
              Direction = 0;
            else
              Direction = NewDirection;
          }
          else
          {
            int NewDirection = Math.Abs(Direction - 180);
            NewDirection = 360 - NewDirection;
            Direction = NewDirection;
          }
        }
      }
    }


    private CollisionResult CollidesWithBounds()
    {
      //TODO: Check bound values, does not work.
      double BoundsWidth = ((Canvas)this.Parent).ActualWidth;
      double BoundsHeight = ((Canvas)this.Parent).ActualHeight;
      if ((this.Left <= 0) && GetNewCoordinates().X < this.Center.X)
        return CollisionResult.Left;
      if ((this.Right >= BoundsWidth) && GetNewCoordinates().X > this.Center.X)
        return CollisionResult.Right;
      if ((this.Top <= 0) && GetNewCoordinates().Y < this.Center.Y)
        return CollisionResult.Top;
      if ((this.Bottom >= BoundsHeight) && GetNewCoordinates().Y > this.Center.Y)
        return CollisionResult.Bottom;
      return CollisionResult.None;
    }

    public Point GetNewCoordinates()
    {
      double x = Math.Round(Center.X + Speed * Math.Cos(Math.PI * Direction / 180.0), 5);
      double y = Math.Round(Center.Y + Speed * Math.Sin(Math.PI * Direction / 180.0), 5);

      return new Point(x, y);
    }


    public void NotifyPropertyChanged(string propertyName)
    {
      if (PropertyChanged != null)
      {
        PropertyChanged(this,
            new PropertyChangedEventArgs(propertyName));
      }
    }
    #endregion
  }
}
